//
//	MiscMergeTemplate.m -- a data container and parser for merge templates
//		Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	


#import <misckit/misckit.h>
#import <misckit/miscmerge.h>
#import <objc/objc-runtime.h>

#import	"_MiscMergeCopyCommand.h"
#import	"_MiscMergeFieldCommand.h"
#import	"_MiscMergeDelayedParseCommand.h"

@implementation MiscMergeTemplate
/*" This class conatins the template that is used by a merge engine.
It performs two functions:  (1) parse a string or text file into the
commands required by a merge ending and (2) act as a container for
the commands once they have been parsed, providing them to a merge
engine as needed.

Typically, MiscMergeTemplate objects are used in a very simple way:  they
are instantiated, given the ASCII text or string to parse, and then passed
to MiscMergeEngine instances as needed.  That's it!

It should be noted that template text which is simply copied from the
template into the merged output (ie. an text outside of a merge command)
is actually turned into a special ªcopyº command by the parsing algorithm.
This allows the merge engine to deal exclusively with MiscMergeCommand
subclasses to perform a merge.  This implementation detail should not
affect anything that would normally be done with this object, but it is
important to understand this fact if attempting to understand the data
structure created by the parsing routines.
"*/

// Note: delimiters may be escaped with a "\".
static char startChar = '«';
static char endChar = '»';

+ (char)startFieldDelimiter
/*" Returns the character used to start a merge command.
"*/
{
	return startChar;
}

+ (char)endFieldDelimiter
/*" Returns the character used to end a merge command.
"*/
{
	return endChar;
}

+ setFieldDelimitersStart:(char)aChar1 end:(char)aChar2
/*" Sets the characters used to start and end a merge command.  Returns
self.  It is highly recommended that %{aChar1} and %{aChar2} be different
characters.
"*/
{	
	startChar = aChar1;
	endChar = aChar2;
	return self;
}

+ getClassForCommand:(MiscString *)aCommand
/*" Given the command string %{aCommand}, this method determines which
MiscMergeCommand subclass implements the merge command.  It returns the
class object needed to create instances of the MiscMergeCommand subclass.

This method works by asking the runtime if it can find Objective-C classes
with specific names.  The name that is looked up is build from the first
word found in %{aCommand}.  The first word is turnd to all lower case, with
the first letter upper case, and then sandwiched between ªMergeº and
ªCommandº.  For example, the merge command ª«If xxx = y»º has the word ªIfº
as the first word.  Thus, the class ªMergeIfCommandº will be searched for.
If the desired class cannot be found, then it is assumed that the merge
command is giving the name of a field wich should be inserted into the
output document.

To avoid name space conflicts, all internal merge commands actually use
a slightly different name.  Thus, there really is no ªMergeIfCommandª to
be found.  This method, when it doesn't find the ªMergeIfCommandº class, will
search for another class, with a private name.  That class will be found.
(If it weren't found, then the default ªfieldº command class would be
returned.)  This allows a programmer to override any built in command.
To override the ªifº command, simply create a ªMergeIfCommandª class
and it will be found before the built in class.  If a programmer wishes
to make a particular command, such as ªomitº, inoperative, this technique
may be used to override with a MiscMergeCommand subclass that does nothing.'

Note that if the command string %{aCommand} contains merge commands inside
itself, then a special ªdelayed commandº class will be returned.  That
class will, during a merge, create an engine, perform a merge on its
text, and then parse itself into the correct type of command.  This allows
merges to contain commands that change depending upon the data records.
"*/
{
// Look for "MergeXxxCommand" class for user overrides.
// Failing that, look for "_MiscMergeXxxCommand" for built-in commands.
// Failing that, we assume we are dealing with a field.
	id theClassName = [MiscString new];
	id foundClass = nil;
	id individualCommand = [aCommand wordNum:0];

	if ([aCommand numOfChar:startChar]) {
		// may have a nested merge to deal with, if so
		// we  use the "delayed merge" class.
		int i; BOOL flag = NO;
		for (i=0; i<[aCommand numOfChar:startChar]; i++) {
			// see if all delimiters are escaped or not
			if ([aCommand charAt:
					([aCommand spotOf:startChar occurrenceNum:i
					caseSensitive:NO] - 1)] != '\\')
				flag = YES;
		}
		// found an unescaped delimiter, so delay the parsing until
		// we are actually doing a merge.
		if (flag) return [_MiscMergeDelayedParseCommand class];
	}

	[individualCommand trimWhiteSpaces];
	[individualCommand toLower];
	[individualCommand capitalizeEachWord];
	theClassName = [[MiscString alloc] initFromFormat:"Merge%sCommand",
			[individualCommand stringValue]];
	foundClass = objc_lookUpClass([theClassName stringValue]);
	if (!foundClass) {
		theClassName = [[MiscString alloc]
				initFromFormat:"_MiscMerge%sCommand",
				[individualCommand stringValue]];
		foundClass = objc_lookUpClass([theClassName stringValue]);
	}
	[theClassName free];
	[individualCommand free];
	return (foundClass ? foundClass : [_MiscMergeFieldCommand class]);
}

- init
/*" Initializes the MiscMergeTemplate instance and returns self.  This is the
designated initializer.
"*/
{
	[super init];
	commands = [[List alloc] init];
	return self;
}

- initWithString:(MiscString *)aString
/*" Initializes the MiscMergeTemplate instance, parses the template from
%{aString}, and returns self.  This method is provided for convenience.
"*/
{
	[self init];
	[self parseFromString:aString];
	return self;
}

- initFromFile:(MiscFile *)aFile
/*" Initializes the MiscMergeTemplate instance, parses the template from
the file pointed to by %{aFile}, and returns self.  This method is provided
for convenience.
"*/
{
	[self init];
	[self parseFromFile:aFile];
	return self;
}

- initFromFileNamed:(MiscString *)aFileName
/*" Initializes the MiscMergeTemplate instance, parses the template from
the file named %{aFileName}, and returns self.  This method is provided
for convenience.
"*/
{
	[self init];
	[self parseFromFileNamed:aFileName];
	return self;
}

- parseFromString:(MiscString *)aString
/*" Parses the MiscMergeTemplate from %{aString}.  Returns self.
"*/
{
	id tempString = [aString copy];
	int count;

	[commands freeObjects];
	while ([tempString length] > 0) {
		MiscString *theText = nil;
		MiscString *theCommand = nil;
		MiscMergeCommand *textCommand, *mergeCommand;

		// cut block of text from the string, remove first delimiter
//		theText = [tempString extractPart:0 useAsDelimiter:startChar];
		// can't use extractPart since we want to allow escaped
		// delimiters in the document.
		count = 0;
		while (!theText) {
			int right = [tempString spotOf:startChar
					occurrenceNum:count caseSensitive:NO] - 1;
			if (right < -1) {
				theText = [tempString copy];
			} else if (right < 0) {
				theText = [MiscString new];
			} else {
				if ([tempString charAt:right] == '\\') {
					count += 1;
				} else {
					theText = [tempString midFrom:0 to:right];
				}
			}
		}
		[tempString replace:[theText stringValue] with:""];
		[tempString replaceFrom:0 length:1 with:""];

		// cut command text from the string, remove closing delimiter

		// **** won't allow nested commands since we don't check to
		// see if there are any start delimiters before we get to the
		// end delimiter.  If there are, then we need to count that
		// many end delimiters to get the nested commands... *****
		// This needs to be fixed for the production version!!!

//		theCommand = [tempString extractPart:0 useAsDelimiter:endChar];
		count = 0;
		{ // scan for end delimiter, allowing for nesting
			int pos, nesting = 0; int right = -2;
			for (pos = 0; pos < [tempString length]; pos++) {
				if ([tempString charAt:pos] == endChar) {
					if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
						if (nesting) nesting--;
						else if (right < -1) {
							right = pos - 1;
						}
					}
				} else if ([tempString charAt:pos] == startChar) {
					if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
						nesting++;
					}
				}
			}
			if (right < -1) theCommand = [tempString copy];
			else if (right < 1) theCommand = [tempString new];
			else theCommand = [tempString midFrom:0 to:right];
		}
		[tempString replace:[theCommand stringValue] with:""];
		[tempString replaceFrom:0 length:1 with:""];

		// parse into command objects
		textCommand = [[_MiscMergeCopyCommand alloc] initFrom:theText];
		mergeCommand = [[[[self class] getClassForCommand:theCommand]
				alloc] initFrom:theCommand];

		// add to our list of commands
		[commands addObject:textCommand];

		// (Note: last time through the loop, this is likely to be a
		// bogus command and really shouldn't be added!  However, it
		// may be OK, if the template actually ends with a command.
		// As such, the only place this affects has a special case
		// in it (see MME+Symbols.m where fields are resolved when
		// leaveDelimiters is on).  We probably won't fix this bug
		// since its effects are 100% benign and it is easier than
		// writing a "real" parser for the templates.  :-)
		[commands addObject:mergeCommand]; // need to turn into cmd obj 1st

		[theText free];
		[theCommand free];
	}
	return self;
}

- parseFromFile:(MiscFile *)aFile
/*" Parses the MiscMergeTemplate from the file pointed to by %{aFileName}.
Returns self.
"*/
{
	MiscString *fileString = [MiscString new];
	[fileString loadFromFile:[aFile fullPath]];
	[self parseFromString:fileString];
	return self;
}

- parseFromFileNamed:(MiscString *)aFileName
/*" Parses the MiscMergeTemplate from the file named %{aFileName}.
Returns self.
"*/
{
	MiscString *fileString = [MiscString new];
	[fileString loadFromFile:[aFileName stringValue]];
	[self parseFromString:fileString];
	[fileString free];
	return self;
}

- commands
/*" Returns a List object containing, in order, all the merge commands
to be executed by the merge engine.
"*/
{
	return commands;
}

- commandAt:(unsigned)index
/*" Returns the ith merge command to be executed by the merge engine.
"*/
{
	return [commands objectAt:index];
}

@end
